// Copyright 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry5.portlet.services;

import java.io.IOException;
import java.util.List;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.servlet.http.Cookie;

import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.internal.portlet.services.ComponentEventLinkEncoderImplPortlet;
import org.apache.tapestry5.internal.portlet.services.PortletContextImpl;
import org.apache.tapestry5.internal.portlet.services.PortletPageResolverImpl;
import org.apache.tapestry5.internal.portlet.services.PortletPageResolverRule;
import org.apache.tapestry5.internal.portlet.services.PortletPageResponseRendererImpl;
import org.apache.tapestry5.internal.portlet.services.PortletRequestGlobalsImpl;
import org.apache.tapestry5.internal.portlet.services.PortletRequestImpl;
import org.apache.tapestry5.internal.portlet.services.response.PortletActionResponse;
import org.apache.tapestry5.internal.portlet.services.response.PortletRenderResponse;
import org.apache.tapestry5.internal.portlet.services.response.PortletResourceResponse;
import org.apache.tapestry5.internal.portlet.services.response.PortletResponse;
import org.apache.tapestry5.internal.services.CookieSource;
import org.apache.tapestry5.internal.services.PageResponseRenderer;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Marker;
import org.apache.tapestry5.ioc.annotations.Primary;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.PipelineBuilder;
import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
import org.apache.tapestry5.portlet.PortletConstants;
import org.apache.tapestry5.portlet.PortletPageResolver;
import org.apache.tapestry5.services.ApplicationGlobals;
import org.apache.tapestry5.services.ApplicationInitializer;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.ComponentEventLinkEncoder;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestGlobals;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
import org.slf4j.Logger;

public final class PortletModule {

    public static void bind(ServiceBinder binder) {
        binder.bind(PortletRequestGlobals.class, PortletRequestGlobalsImpl.class);
        binder.bind(PortletConfigProvider.class, PortletConfigProviderImpl.class);

        binder.bind(ComponentEventLinkEncoder.class, ComponentEventLinkEncoderImplPortlet.class).withId(
            "ComponentEventLinkEncoderImplPortlet");

        binder.bind(PageResponseRenderer.class, PortletPageResponseRendererImpl.class).withId(
            "PortletPageResponseRendererImpl");
    }

    public void contributeServiceOverride(MappedConfiguration<Class, Object> configuration,
                                          @Local ComponentEventLinkEncoder componentEventLinkEncoder,
                                          @Local PageResponseRenderer pageResponseRenderer,
                                          @Local CookieSource cookieSource) {
        configuration.add(ComponentEventLinkEncoder.class, componentEventLinkEncoder);
        configuration.add(PageResponseRenderer.class, pageResponseRenderer);
        configuration.add(CookieSource.class, cookieSource);
    }

    public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration) {
        //        configuration.add(SymbolConstants.FORCE_ABSOLUTE_URIS, "true");
        configuration.add(SymbolConstants.SUPPRESS_REDIRECT_FROM_ACTION_REQUESTS, "true");
    }

    /**
     * Builds a shadow of the PortletRequestGlobals.portletRequest property. Note again that the shadow can be 
     * an ordinary singleton, even though PortletRequestGlobals is perthread.
     */
    @Marker(PortletInterfaceProvider.class)
    public PortletRequest buildPortletRequest(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "portletRequest", PortletRequest.class);
    }

    /**
     * Builds a shadow of the PortletRequestGlobals.actionRequest property. Note again that the shadow can be 
     * an ordinary singleton, even though PortletRequestGlobals is perthread.
     */
    @Marker(ActionInterfaceProvider.class)
    public ActionRequest buildActionRequest(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "actionRequest", ActionRequest.class);
    }

    /**
     * Builds a shadow of the PortletRequestGlobals.renderRequest property. Note again that the shadow can be 
     * an ordinary singleton, even though PortletRequestGlobals is perthread.
     */
    @Marker(RenderInterfaceProvider.class)
    public RenderRequest buildRenderRequest(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "renderRequest", RenderRequest.class);
    }

    @Marker(ResourceInterfaceProvider.class)
    public ResourceRequest buildResourceRequest(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "resourceRequest", ResourceRequest.class);
    }

    /**
     * Builds a shadow of the PortletRequestGlobals.portletResponse property. Note again that the shadow can be 
     * an ordinary singleton, even though PortletRequestGlobals is perthread.
     */
    @Marker(PortletInterfaceProvider.class)
    public PortletResponse buildPortletResponse(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "portletResponse", PortletResponse.class);
    }

    /**
     * Builds a shadow of the PortletRequestGlobals.actionResponse property. Note again that the shadow can be 
     * an ordinary singleton, even though PortletRequestGlobals is perthread.
     */
    @Marker(ActionInterfaceProvider.class)
    public ActionResponse buildActionResponse(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "actionResponse", ActionResponse.class);
    }

    /**
     * Builds a shadow of the PortletRequestGlobals.renderResponse property. Note again that the shadow can be 
     * an ordinary singleton, even though PortletRequestGlobals is perthread.
     */
    @Marker(RenderInterfaceProvider.class)
    public RenderResponse buildRenderResponse(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "renderResponse", RenderResponse.class);
    }

    @Marker(ResourceInterfaceProvider.class)
    public ResourceResponse buildResourceResponse(

    PropertyShadowBuilder shadowBuilder,

    PortletRequestGlobals portletRequestGlobals) {
        return shadowBuilder.build(portletRequestGlobals, "resourceResponse", ResourceResponse.class);
    }

    public PortletApplicationInitializer build(Logger logger,

    List<PortletApplicationInitializerFilter> configuration,

    @InjectService("ApplicationGlobals") final ApplicationGlobals applicationGlobals,

    @InjectService("ApplicationInitializer") final ApplicationInitializer initializer,

    @InjectService("PipelineBuilder") PipelineBuilder builder) {
        PortletApplicationInitializer terminator = new PortletApplicationInitializer() {

            @Override
            public void initializeApplication(PortletContext context) {
                // this does not work since the app globals do not know of portlets  
                //applicationGlobals.store(context);

                initializer.initializeApplication(new PortletContextImpl(context));
            }
        };

        return builder.build(logger, PortletApplicationInitializer.class, PortletApplicationInitializerFilter.class,
            configuration, terminator);
    }

    public PortletPageResolver build(ComponentClassResolver componentClassResolver,
                                     PortletConfigProvider portletConfigProvider,
                                     List<PortletPageResolverRule> configuration) {
        return new PortletPageResolverImpl(componentClassResolver, portletConfigProvider, configuration);
    }

    public void contributePortletPageResolver(OrderedConfiguration<PortletPageResolverRule> configuration) {
        //    		configuration.add("default", new PortletPageResolverRule("", "text/html", "view", "normal", "start"), "after:*");
        //    		configuration.add("Index", new PortletPageResolverRule("TapestryPortlet", "text/html", "view", "normal", "Index"));
        //    		configuration.add("Aboutxhr", new PortletPageResolverRule("TapestryPortlet", "application/json", "view", "normal", "About"));
        //    		configuration.add("About", new PortletPageResolverRule("TapestryPortlet", "text/html", "view", "normal", "About"));
        //    		configuration.add("Contact", new PortletPageResolverRule("TapestryPortlet", "text/html", "view", "normal", "Contact"));
    }

    public PortletActionRequestHandler build(Logger logger,

    List<PortletActionRequestFilter> configuration,

    @InjectService("PipelineBuilder") PipelineBuilder builder,

    final PortletPageResolver pageResolver,

    @InjectService("RequestGlobals") final RequestGlobals requestGlobals,

    @InjectService("PortletRequestGlobals") final PortletRequestGlobals portletRequestGlobals,

    @InjectService("RequestHandler") final RequestHandler handler,

    @Primary final SessionPersistedObjectAnalyzer analyzer) {

        PortletActionRequestHandler terminator = new PortletActionRequestHandler() {

            @Override
            public boolean service(ActionRequest actionRequest, ActionResponse actionResponse) throws IOException {
                String pageName = "";
                if (this.isStateChange(actionRequest)) {
                    pageName = pageResolver.resolve(actionRequest);
                }
                else {
                    pageName = actionRequest.getParameter(PortletConstants.PORTLET_PAGE);
                }

                Request request = new PortletRequestImpl(actionRequest, pageName, analyzer);
                PortletActionResponse response = new PortletActionResponse(actionResponse, request);

                requestGlobals.storeRequestResponse(request, response);
                portletRequestGlobals.storePortletResponse(response);
                portletRequestGlobals.storeActionRequestResponse(actionRequest, actionResponse);

                return handler.service(request, response);
            }

            /**
             * Returns true if the portlet mode or the window state has changed since
             * (if both are null, the state has not changed).
             * The values stored previously (during an action request) are compared to
             * the current values.
             */
            private boolean isStateChange(ActionRequest actionRequest) {
                String mode = actionRequest.getParameter(PortletConstants.PORTLET_MODE);
                String windowState = actionRequest.getParameter(PortletConstants.WINDOW_STATE);

                return (mode != null || windowState != null)
                    && !(actionRequest.getPortletMode().toString().equals(mode) && actionRequest.getWindowState()
                        .toString().equals(windowState));
            }
        };

        return builder.build(logger, PortletActionRequestHandler.class, PortletActionRequestFilter.class,
            configuration, terminator);
    }

    public PortletRenderRequestHandler build(Logger logger, List<PortletRenderRequestFilter> configuration,

    @InjectService("PipelineBuilder") PipelineBuilder builder, final PortletPageResolver pageResolver,

    @InjectService("RequestGlobals") final RequestGlobals requestGlobals,

    @InjectService("PortletRequestGlobals") final PortletRequestGlobals portletRequestGlobals,

    @InjectService("RequestHandler") final RequestHandler handler,

    @Primary final SessionPersistedObjectAnalyzer analyzer) {

        PortletRenderRequestHandler terminator = new PortletRenderRequestHandler() {

            @Override
            public boolean service(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException {
                String pageName = pageResolver.resolve(renderRequest);

                Request request = new PortletRequestImpl(renderRequest, pageName, analyzer);
                PortletRenderResponse response = new PortletRenderResponse(renderResponse, request);

                requestGlobals.storeRequestResponse(request, response);
                portletRequestGlobals.storePortletResponse(response);
                portletRequestGlobals.storeRenderRequestResponse(renderRequest, renderResponse);

                return handler.service(request, response);
            }
        };

        return builder.build(logger, PortletRenderRequestHandler.class, PortletRenderRequestFilter.class,
            configuration, terminator);
    }

    public PortletResourceRequestHandler build(Logger logger,

    List<PortletResourceRequestFilter> configuration,

    @InjectService("PipelineBuilder") PipelineBuilder builder,

    final PortletPageResolver pageResolver,

    @InjectService("RequestGlobals") final RequestGlobals requestGlobals,

    @InjectService("PortletRequestGlobals") final PortletRequestGlobals portletRequestGlobals,

    @InjectService("RequestHandler") final RequestHandler handler,

    @Primary final SessionPersistedObjectAnalyzer analyzer) {

        PortletResourceRequestHandler terminator = new PortletResourceRequestHandler() {

            @Override
            public boolean service(ResourceRequest resourceRequest, ResourceResponse resourceResponse)
                                                                                                      throws IOException,
                                                                                                      PortletException {
                String pageName = resourceRequest.getParameter(PortletConstants.PORTLET_PAGE);
                if (InternalUtils.isBlank(pageName)) {
                    pageName = pageResolver.resolve(resourceRequest);
                }

                Request request = new PortletRequestImpl(resourceRequest, pageName, analyzer);
                PortletResourceResponse response = new PortletResourceResponse(resourceResponse, request);

                requestGlobals.storeRequestResponse(request, response);
                portletRequestGlobals.storePortletResponse(response);
                portletRequestGlobals.storeResourceResponseRequest(resourceRequest, resourceResponse);

                return handler.service(request, response);
            }
        };

        return builder.build(logger, PortletResourceRequestHandler.class, PortletResourceRequestFilter.class,
            configuration, terminator);

    }

    public CookieSource buildPortletCookieSource() {
        return new CookieSource() {

            @Override
            public Cookie[] getCookies() {
                return new Cookie[0];
            }

        };
    }

    //    public void contributeComponentEventResultProcessor(@Traditional @ComponentInstanceProcessor ComponentEventResultProcessor componentInstanceProcessor,
    //                                                        MappedConfiguration<Class, ComponentEventResultProcessor> configuration) {
    //        configuration.overrideInstance(StreamResponse.class, PortletStreamResponseResultProcessor.class);
    //    }
}
